
#ifndef _HULUWA_LOG_HPP_
#define _HULUWA_LOG_HPP_

#include <iostream>
#include <string>
#include <fstream>
#include "Exception.hpp"
#include "Utility.hpp"  // for GetTimeStamp

namespace huluwa {

/** 
 * @brief   Huluwa Logging System.
 *
 * Huluwa Logging System is a static system, we should initialize it at first 
 * and remember to close it.
 */
class Log {

public:

    static const int DEBUG   = 1;
    static const int INFO    = 2;
    static const int WARNING = 3;
    static const int ERROR   = 4;
    static const int FATAL   = 5;

    /// Open Huluwa Logging System.
    static void Open(int level = DEBUG, const char *logFile = "huluwa.log", bool toScreen = false)
    {
        if (!initialized_) {
            level_ = level;
            toScreen_ = toScreen;
            logFilename_ = logFile;
            logFile_.open(logFilename_.c_str(), std::ios::trunc | std::ios::out);

            if (!logFile_.is_open() || logFile_.fail()) {
                logFile_.close();
                throw Exception("Error on initializing Logging System, failed to create log file -- " + logFilename_);
            }

            initialized_ = true;
            Debug("Open Logging System.");
        }
        else {
            Warning("Try to reopen Logging System, ingore...");
        }
    }

    /// Close Huluwa Logging System.
    static void Close()
    {
        if (logFile_.is_open()) {
            Debug("Close Logging System.");
            logFile_.close();
        }

        initialized_ = false;
    }

    /// Output debug message.
    static void Debug(const std::string& msg) 
    {
        if (!initialized_)
            throw Exception("The Logging System must be initialized at first.");

        if (IsDebug()) {
            WriteLog(GetTimeStamp() + " : DEBUG : " + msg);
        }
    }

    /// Output info message.
    static void Info(const std::string& msg)
    {
        if (!initialized_)
            throw Exception("The Logging System must be initialized at first.");

        WriteLog(GetTimeStamp() + " : " + msg);
    }

    /// Output warning message.
    static void Warning(const std::string& msg)
    {
        if (!initialized_)
            throw Exception("The Logging System must be initialized at first.");

        if (IsWarning()) {
            WriteLog(GetTimeStamp() + " : WARNING : " + msg);
        }
    }

    /// Output error message.
    static void Error(const std::string& msg)
    {
        if (!initialized_)
            throw Exception("The Logging System must be initialized at first.");

        if (IsError()) {
            WriteLog(GetTimeStamp() + " : ERROR : " + msg);
        }
    }
    
    /// Output fatal message.
    static void Fatal(const std::string& msg)
    {
        if (!initialized_)
            throw Exception("The Logging System must be initialized at first.");

        if (IsFatal()) {
            WriteLog(GetTimeStamp() + " : FATAL : " + msg);
        }
    }

    /// Detemine whether the log level is debug.
    static bool IsDebug()
    {
        return level_ <= DEBUG;
    }

    /// Detemine whether the log level is info.
    static bool IsInfo()
    {
        return level_ <= INFO;
    }

    /// Detemine whether the log level is warning.
    static bool IsWarning()
    {
        return level_ <= WARNING;
    }

    /// Detemine whether the log level is error.
    static bool IsError()
    {
        return level_ <= ERROR;
    }

    /// Detemine whether the log level is fatal.
    static bool IsFatal()
    {
        return level_ <= FATAL;
    }

    /// Get log level.
    static int GetLevel()
    {
        return level_;
    }

    /// Set log level.
    static void SetLevel(int level)
    {
        level_ = level;
    }

    /// Detemine whether the log message is output to screen.
    static bool IsToScreen()
    {
        return toScreen_;
    }
    
    /// Set whether the log message is output to screen.
    static void SetToScreen(bool toScreen)
    {
        toScreen_ = toScreen;
    }

private:
    Log();
    ~Log();

    /// Write log message.
    static void WriteLog(const std::string& msg)
    {
        logFile_ << msg << std::endl;
        logFile_.flush();

        if (toScreen_)
            std::cout << msg << std::endl;
    }

    static std::string logFilename_;
    static int level_;
    static bool toScreen_;
    static bool initialized_;
    static std::ofstream logFile_;
}; // end of class Log

} // end of namespace huluwa

#endif 
